package it.uniroma2.svd.writer;

import it.uniroma2.svd.ExecSVD;

import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;

import it.uniroma2.svd.writer.BinaryMatrix;
import it.uniroma2.svd.writer.SparseBinaryMatrix;



public class SparseBinaryMatrix<T> implements BinaryMatrix<T>{
	private RandomAccessFile _matrixFile;
	private ByteArrayInputStream b_arr;
	private DataInputStream b_in;
	private int n_row = -1;
	private int n_col = -1;
	private boolean sized = false;
	private Vector<Long> posMap;	
	private int sizeType;
	private Class<T> tClass;
	private Method readMethod;
	private Method writeMethod;
	private Long currentElem;
	private int nonZeroElem;
	private boolean bycol;
	private boolean fullLoad;
	public enum Operator {SUM, DIV, MULT}
	

	public SparseBinaryMatrix(Class<T> c){
		currentElem = 0L;
		nonZeroElem = 0;
		tClass = c;
		setType();
	}
	public void openFile(String file, String mode) throws IOException {
			openFile(file, mode, false);
		
	}
	public void openFile(String file, String mode, boolean deleteExist) throws IOException{ //mode "r" o "rw"
		//Se è solo in lettura non deve cancellare il file esistente
		if (mode.equals("r"))
			deleteExist=false;
		openFile(file, mode, true, false, deleteExist);
	}
	public void openFile(String file, String mode, boolean _bycol, boolean _fullLoad, boolean deleteExist) throws IOException{ //mode "r" o "rw"
		bycol = _bycol;
		fullLoad = _fullLoad;
		File _file = new File(file);
		boolean exist =_file.exists();
		if(exist && deleteExist){
			_file.delete();
			exist = false;
		}
			
		_matrixFile = new RandomAccessFile(file,mode);
		posMap = new Vector<Long>();
		if(!exist){
			_matrixFile.setLength(12);
			_matrixFile.seek(12);
		}else{
			_matrixFile.seek(0);
			n_row = _matrixFile.readInt();
			n_col = _matrixFile.readInt();
			readPosition();
		}
//		System.out.println("*** MAX Integer "+Integer.MAX_VALUE);
		if(fullLoad){
			int size = (int)_matrixFile.length();
			_matrixFile.seek(0);
			byte[] buf = new byte[size];
			System.out.println(">>> Reading complete file; size: "+size);
			int read = _matrixFile.read(buf,0,size);
	//		int read = 0;
	//		for (int i = 0; i < size ; i++){
	//			try{
	//				buf[i]=_matrixFile.readByte();
	//			}catch(EOFException e){
	//				e.printStackTrace();
	//				System.out.println(">>>> ERROR during load sparse matrix i: "+i+" read: "+read+"  size: "+ size+" buf: "+buf.length);
	//			}
	//			read++;
	//		}
			if(read != size){
				System.out.println(">>>> ERROR during load sparse matrix read: "+read+"  size: "+ size+" buf: "+buf.length);
				for(int i = 0; i < 10 ; i++){
					System.out.print(buf[i]);
				}
				System.out.print("\n");
				System.exit(1);
			}
			b_arr = new ByteArrayInputStream(buf);
			b_in = new DataInputStream(b_arr);
	//		if(sized){
	//			long len = 8 + ((long)n_row * (long)n_col * (long)sizeType);
	//			_matrixFile.setLength(len);
	//			writeSize();
	//		}
		}
	}
	private void readPosition() throws IOException{
		_matrixFile.seek(12);
		int items;
		if(bycol){
			items = n_col;
		}else{
			items = n_row;
		}
		//System.out.println("Items: "+items);
		for( int i = 0; i < items ; i++){
			long pos = _matrixFile.getFilePointer();
			//System.out.println("pos: ("+i+")"+pos);
			int it = _matrixFile.readInt();
			_matrixFile.skipBytes(it*(4+sizeType));
			posMap.add(pos);
		}
	}
	public void closeFile() throws IOException{
		_matrixFile.close();
	}
	
	public void writeInt(Integer i) throws IOException{_matrixFile.writeInt(i);}
	public void writeFloat(Float i) throws IOException{_matrixFile.writeFloat(i);}
	public void writeDouble(Double i) throws IOException{_matrixFile.writeDouble(i);}
	
	public Integer readInt() throws IOException{
		if(fullLoad)
			return b_in.readInt();
		else
			return _matrixFile.readInt();
	}
	public Float readFloat() throws IOException{
		if(fullLoad)
			return b_in.readFloat();
		else
			return _matrixFile.readFloat();
	}
	
	public Double readDouble() throws IOException{
		if(fullLoad)
			return b_in.readDouble();
		else
			return _matrixFile.readDouble();
	}
	public void setSize(int row, int col){
		n_col = col;
		n_row = row;
		sized = true;
	}
	public void setCol(int col){
		n_col = col;
	}
	public void setType(){
		try {
			if(tClass.equals(Integer.class)){
				sizeType = 4;
				readMethod = this.getClass().getMethod("readInt");
				writeMethod = this.getClass().getMethod("writeInt", new Class[]{Integer.class});
			}else if(tClass.equals(Float.class)){
				readMethod = this.getClass().getMethod("readFloat");
				writeMethod = this.getClass().getMethod("writeFloat", new Class[]{Float.class});
				sizeType = 4;
			}else if(tClass.equals(Double.class)){
				readMethod = this.getClass().getMethod("readDouble");
				writeMethod = this.getClass().getMethod("writeDouble", new Class[]{Double.class});
				sizeType = 8;
			} 
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		}
		
	}
	
	public void readSize() throws IOException{
		_matrixFile.seek(0);
		n_row = _matrixFile.readInt();
		n_col = _matrixFile.readInt();
		
	}
	
	public void writeSize() throws IOException{
		_matrixFile.seek(0);
		_matrixFile.writeInt(n_row);
		_matrixFile.writeInt(n_col);
		_matrixFile.writeInt(nonZeroElem);
	}
	
//	private long getPos(int row, int col){
//		return 12 + (((long)row * (long)n_col) + col )* (long)sizeType;//( (row * n_col) + col ) * sizeType;
//	}
		
	public T getElement(int row, int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		if(bycol){
			Long pos = posMap.elementAt(col);
			int i = 0;
			int read;
			if(fullLoad){
				if(currentElem > pos){
					b_in.reset();
					b_in.skip(pos);
				}else{
					b_in.skip(pos-currentElem);
				}
				currentElem = pos;
				int limit = b_in.readInt();
				currentElem += 4;
				while((read =b_in.readInt()) < row && i++ < limit){
					currentElem += 4;
					b_in.skipBytes(sizeType);
					currentElem += sizeType;
				}
				currentElem += 4;
			}else{
				_matrixFile.seek(pos);
				int limit = _matrixFile.readInt();
				try{
				while((read =_matrixFile.readInt()) < row && i++ < limit){
					_matrixFile.skipBytes(sizeType);
				}
				}catch(java.io.EOFException e){
					return null;
				}
			}
			
			if (read == row){
				currentElem += sizeType;
				return (T) readMethod.invoke(this, null);
			}
			
		}else{
			
			
			Long pos = posMap.elementAt(row);
			int i = 0;
			int read;
			if(fullLoad){
				if(currentElem > pos){
					b_in.reset();
					b_in.skip(pos);
				}else{
					b_in.skip(pos-currentElem);
				}
				int limit = b_in.readInt();
				currentElem += 4;
				while((read =b_in.readInt()) < col && i++ < limit){
					currentElem += 4;
					b_in.skipBytes(sizeType);
					currentElem += sizeType;
				}
				
			}else{
				_matrixFile.seek(pos);
				int limit = _matrixFile.readInt();
				while((read =_matrixFile.readInt()) < col && i++ < limit){
					_matrixFile.skipBytes(sizeType);
				}
			}
				
			if (read == col){
				currentElem += sizeType;
				return (T) readMethod.invoke(this, null);
			}
		}
		return null;
	}
	public T[] getFullRow(int row) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		return null;
	}
	public T[] getFullCol(int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		return null;
	}
	public TreeMap<Integer, T> getSparseRow(int row) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		TreeMap<Integer, T> ret = new TreeMap<Integer, T>();
		if(bycol){
			for(int i  = 0; i < n_col ;i++){
				T elem = getElement(row, i);
				if(elem != null ){
					ret.put(i, elem);
				}
			}
		}else{
			Long pos = posMap.elementAt(row);
			if(fullLoad){
				if(currentElem > pos){
					b_in.reset();
					b_in.skip(pos);
				}else{
					b_in.skip(pos-currentElem);
				}
				int count = b_in.readInt();
				currentElem += 4;
				for (int i = 0; i < count ; i++){
					int col = b_in.readInt();
					currentElem += 4;
					T val = (T) readMethod.invoke(this, null);
					currentElem += sizeType;
					ret.put(col, val);
				}
			}else{
				_matrixFile.seek(pos);
				int count = _matrixFile.readInt();
				for (int i = 0; i < count ; i++){
					int col = _matrixFile.readInt();
					T val = (T) readMethod.invoke(this, null);
					ret.put(col, val);
				}
			}
		}
		return ret;
	}
	
	public TreeMap<Integer, T> getSparseColTranspose(int col, String pathMatrix) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, IOException{
		try {
			SparseBinaryMatrix<Float> sbmOldT = new SparseBinaryMatrix<Float>(Float.class); 
			sbmOldT.openFile(pathMatrix+"_T", "r"); 
			TreeMap<Integer, T> vet = (TreeMap<Integer, T>) sbmOldT.getSparseCol(col);
			sbmOldT.closeFile();
			return vet;
		} catch (Exception e) {
			ExecSVD execSVD = new ExecSVD();
			execSVD.transposeMatrix(pathMatrix);
			
		}
		return getSparseColTranspose(col,pathMatrix);
		
	}

	public TreeMap<Integer, T> getSparseCol(int col) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		TreeMap<Integer, T> ret = new TreeMap<Integer, T>();
		if(bycol){
			Long pos = posMap.elementAt(col);
			if(fullLoad){
				if(currentElem > pos){
					b_in.reset();
					b_in.skip(pos);
				}else{
					b_in.skip(pos-currentElem);
				}
				int count = b_in.readInt();
				currentElem += 4;
				for (int i = 0; i < count ; i++){
					int row = b_in.readInt();
					currentElem += 4;
					T val = (T) readMethod.invoke(this, null);
					currentElem += sizeType;
					ret.put(row, val);
				}
			}else{
				_matrixFile.seek(pos);
				int count = _matrixFile.readInt();
				for (int i = 0; i < count ; i++){
					int row = _matrixFile.readInt();
					T val = (T) readMethod.invoke(this, null);
					ret.put(row, val);
				}
			}
		}else{
			for(int i  = 0; i < n_row ;i++){
				T elem = getElement(i, col);
				if(elem != null ){
					ret.put(i, elem);
				}
			}
		}
		return ret;
	}
	
	public void setSparse(SortedMap<Integer, T> items) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException{
		posMap.add(_matrixFile.getFilePointer());
		nonZeroElem += items.size();
		_matrixFile.writeInt(items.size());
		for(Integer key : items.keySet()){
			_matrixFile.writeInt(key);
			writeMethod.invoke(this, items.get(key));
		}
		currentElem++;
	}

	public int getCols(){return n_col;}
	public int getRows(){return n_row;}
	
	public void printAll() throws IOException{
		_matrixFile.seek(0);
		int rows= _matrixFile.readInt();
		int cols = _matrixFile.readInt();
		if(bycol){int tmp = rows; rows = cols; cols = tmp;} //cambio righe con colonne e la leggo come se fosse trasposta
		int items = _matrixFile.readInt();
		System.out.println(cols+" "+rows+" "+items);
		int countrow = 0 ;
		 
		while(countrow++ < rows){
			int rowitems = _matrixFile.readInt();
			int countitem = 0 ;
			System.out.println(rowitems);
			while(countitem++ < rowitems){
				System.out.println(_matrixFile.readInt()+" "+_matrixFile.readFloat());
			}
		}
	}
	
	public void printOctave(String path) throws IOException, IllegalAccessException, InvocationTargetException{
		_matrixFile.seek(0);
		
		int rows= _matrixFile.readInt();
		int cols = _matrixFile.readInt();
		if(bycol){int tmp = rows; rows = cols; cols = tmp;} //cambio righe con colonne e la leggo come se fosse trasposta
		int items = _matrixFile.readInt();
		FileWriter matrixOut = new FileWriter(path+".mtx");
		matrixOut.write("%%MatrixMarket matrix coordinate real general \n");
		
		matrixOut.write(rows+" "+cols+" "+items+"\n");

		
		int countrow = 0 ;
		 
		while(countrow++ < rows){
			int rowitems = _matrixFile.readInt();
			int countitem = 0;
			while(countitem++ < rowitems){
				matrixOut.write(countrow +" "+(_matrixFile.readInt()+1)+" "+_matrixFile.readFloat()+"\n");
			}
		}
		matrixOut.close();
	}
	
	
//	public T sum(T a, T b){Double res = ((Double)a+(Double)b); return (T)res;}
	public Double sum(Double a, Double b){Double res = a+b; return res;}
	public Float sum(Float a, Float b){Float res = a+b; return res;}
	public Integer sum(Integer a, Integer b){Integer res = a+b; return res;}
//	public T sum(Object a, Object b){Double res = ((Double)a+(Double)b); return (T)res;}
	
	public Double mult(Double a, Double b){return a*b;}
	public Float mult(Float a, Float b){return a*b;}
	public Integer mult(Integer a, Integer b){return a*b;}
//	public T mult(Object a, Object b){Double res = ((Double)a*(Double)b); return (T)res;}
	
//	public T div(T a, T b){Double res = ((Double)a/(Double)b); return (T)res;}
//	public T div(Object a, Object b){Double res = ((Double)a/(Double)b); return (T)res;}
	public Double div(Double a, Double b){return a/b;}
	public Float div(Float a, Float b){return a/b;}
	public Integer div(Integer a, Integer b){return a/b;}
	
	public static void main(String[] args) {
		SparseBinaryMatrix<Float> mat = new SparseBinaryMatrix<Float>(Float.class);
		
		if(args.length!=1){
			System.out.println("Usage: "+SparseBinaryMatrix.class+" <matrix>");
			System.exit(1);
		}
//		TimeUtility tu = new TimeUtility();
//		try {
//			tu.start();
//			System.out.println("openfile "+args[0]);
//			mat.openFile(args[0], "r",true,false);
//			tu.stop();
//			tu.println();
//			tu.start();
//			mat.printAll();
//			tu.stop();
//			tu.println();
//			tu.start();
//		} catch (IOException e) {
//			e.printStackTrace();
//		} catch (IllegalArgumentException e) {
//			e.printStackTrace();
//		}
	}
	public void setElement(int row, int col, T value) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		
	}
	public void setFullCol(int col, T[] colelem) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		// TODO Auto-generated method stub
		
	}
	public void setFullRow(int row, T[] values) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		// TODO Auto-generated method stub
		
	}
	public void setSparseCol(int col, Map<Integer, T> values) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		// TODO Auto-generated method stub
	}
	public void setSparseRow(int row, Map<Integer, T> values) throws IOException, IllegalArgumentException, IllegalAccessException, InvocationTargetException {
		// TODO Auto-generated method stub
		
	}

}
